package json5

import (
	"bufio"
	"io"
	"strings"
)

// Чтение элементов формата JSON.
// Возвращает:
// - []Member      - объект JSON - массив пар "имя: элемент";
// - []interface{} - массив JSON - массив значений или объектов JSON;
// - значение      - строка, число, литерал (true/false/nil).
func ParseJson(r io.Reader) (interface{}, error) {
	bufr := bufio.NewReader(r)
	json, _, err := parseElement(bufr, 0)
	if err == io.EOF {
		err = nil
	}
	return json, err
}

func parseElement(bufr *bufio.Reader, stop byte) (interface{}, byte, error) {
	for {
		b, err := bufr.ReadByte()
		if err != nil {
			return nil, 0, err
		}
		//      SPACE        NL (\n)       CR (\r)       TAB (\t)
		if b == 0x020 || b == 0x00A || b == 0x00D || b == 0x009 {
			continue
		}
		// value
		switch b {
		case '{':
			return parseObject(bufr)
		case '[':
			return parseArray(bufr)
		case ',', stop:
			return nil, b, nil
		default:
			return parseString(bufr, b, ',', stop)
		}
	}
}

func parseObject(bufr *bufio.Reader) ([]Member, byte, error) {
	var object []Member
	// members
	for {
		// member
		member := Member{}
		// name
		name, b, err := parseString(bufr, 0, ':', '}')
		if err != nil {
			return nil, 0, err
		}
		member.Name = name
		if b == ':' {
			// element
			member.Element, b, err = parseElement(bufr, '}')
			if err != nil {
				return nil, 0, err
			}
		}
		if len(name) > 0 {
			object = append(object, member)
		}
		if b == '}' {
			return object, 0, nil
		}
	}
}

func parseArray(bufr *bufio.Reader) ([]interface{}, byte, error) {
	var array []interface{}
	for {
		// element
		element, b, err := parseElement(bufr, ']')
		if err != nil {
			return nil, 0, err
		}
		if element != nil {
			array = append(array, element)
		}
		if b == ']' {
			return array, 0, nil
		}
	}
}

func parseString(bufr *bufio.Reader, b, stop1, stop2 byte) (string, byte, error) {
	builder := new(strings.Builder)
	var quote byte = 0
	var err error
	for {
		if b == '\\' {
			bb, err := bufr.ReadByte()
			if err != nil {
				if err == io.EOF {
					break
				}
				return "", 0, err
			}
			switch bb {
			case 'b':
				bb = '\b'
			case 'f':
				bb = '\f'
			case 'n':
				bb = '\n'
			case 'r':
				bb = '\r'
			case 't':
				bb = '\t'
			case 'v':
				bb = '\v'
			case '0':
				bb = 0
			}
			err = builder.WriteByte(bb)
			if err != nil {
				return "", 0, err
			}
		} else if b == '\'' || b == '"' {
			if quote == b {
				quote = 0
			} else if quote == 0 {
				quote = b
			} else {
				err := builder.WriteByte(b)
				if err != nil {
					return "", 0, err
				}
			}
		} else if (b == stop1 || b == stop2 || b == ',') && quote == 0 {
			break
		} else if b != 0 {
			err := builder.WriteByte(b)
			if err != nil {
				return "", 0, err
			}
		}
		b, err = bufr.ReadByte()
		if err != nil {
			if err == io.EOF {
				break
			}
			return "", 0, err
		}
	}
	s := strings.TrimSpace(builder.String())
	return s, b, err
}
